/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package modeloprediccion;

import java.sql.*;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.HashMap;
import motorprediccion.DatoPosicional;
import motorprediccion.DatoProcesado;
import motorprediccion.Parada;
import motorprediccion.Prediccion;
import motorprediccion.Recorrido;

/**
 *
 * @author dani
 */
public class ModeloPrediccion extends Modelo{
    private HashMap parametros;
    private Recorrido recorrido;
    private HashMap<Integer,ArrayList<Prediccion>> predicciones;
    private ArrayList<Integer> x;
    private ArrayList<Float> y;
    private float m,b;

    public ModeloPrediccion(Recorrido r) throws SQLException {
        // Inicializador del modelo
        predicciones = new HashMap<Integer,ArrayList<Prediccion>>();
        x = new ArrayList<Integer>(0);
        y = new ArrayList<Float>(0);
        m = 0; b= 0;
        recorrido = r;
        parametros = new HashMap();
        cargarTiemposBase();
    }

    public void procesarDatos(ArrayList<DatoProcesado> nuevosDatos) throws SQLException {
    //Procesa las nuevas posiciones de las unidades y calcula los tiempos y errores
        for(int i=0; i < nuevosDatos.size(); i++) {
            ParametrosParada pp = (ParametrosParada) parametros.get(nuevosDatos.get(i).getParada().getId());

            float errorBase = nuevosDatos.get(i).getTiempoSegundos() - pp.getTiempoBase();
            float errorPrediccion = nuevosDatos.get(i).getTiempoSegundos() - pp.getPrediccion();

            pp.actualizarParametros(errorBase, errorPrediccion);
        }

        estimarTiempos(nuevosDatos.get(0));

        calcularErroresPrediccion(nuevosDatos);
    }

    public int obtenerPrediccion(Parada origen, Parada destino) {
        //Funcion que suma los tiempos desde la parada origen a la parada destino
        Parada i = origen;
        Integer tiempoEspera = 0;

        while(i != destino) {
            ParametrosParada pp = (ParametrosParada) parametros.get(i.getId());
            tiempoEspera += (int) pp.getPrediccion();
            //tiempoEspera += (int) pp.getTiempoBase();
            i = this.recorrido.obtenerParadaSiguiente(i.getId());
        }

        return tiempoEspera;
    }

    private void cargarTiemposBase() throws SQLException {
        Connection conexionBD = null;

          try {
            conexionBD = DriverManager.getConnection("jdbc:postgresql://localhost/sistemaprediccion",
                                            "postgres", "postgres");
          }
          catch (SQLException se) {
            System.out.println("Couldn't connect: print out a stack trace and exit.");
            se.printStackTrace();
            System.exit(1);
          }

        Statement s = null;
        try {
          s = conexionBD.createStatement();
        }
        catch (SQLException se) {
          System.out.println("Error en la BD");
          se.printStackTrace();
          System.exit(1);
        }

        String consulta = "SELECT idparada, promedio, desstd, orden FROM motorprediccion.promedios order by orden asc;";
        ResultSet resultado = null;
        try {
          resultado = s.executeQuery(consulta);
        }
        catch (SQLException se) {
          System.out.println("SQL Error");
          se.printStackTrace();
          System.exit(1);
        }
        //Ver posibilidad de unificar excepciones

        while(resultado.next()) {
            int idparada = resultado.getInt("idparada");
            double promedio = resultado.getDouble("promedio");
            ParametrosParada parametro = new ParametrosParada(recorrido.obtenerParada(idparada), (float) promedio);
            this.parametros.put(idparada,parametro);
        }
    }

    private void estimarTiempos(DatoProcesado dato) {
        //funcion que calcula los tiempos y los acumula en predicciones.
        Parada siguiente = recorrido.obtenerParadaSiguiente(dato.getParada().getId());

        ArrayList<Prediccion> p = (ArrayList<Prediccion>)predicciones.get(dato.getIdcolectivo());

        if(p == null)
        {
            predicciones.put(dato.getIdcolectivo(), new ArrayList<Prediccion>(0));
            p = (ArrayList<Prediccion>)predicciones.get(dato.getIdcolectivo());
        }

        for(int i = recorrido.obtenerOrden(dato.getParada()); i < recorrido.getCantidadParadas(); i++)
        {
            Prediccion prediccion = new Prediccion(dato.getParada(),siguiente,dato.getHoraArribo(),dato.getIdcolectivo());
            GregorianCalendar horaArribo = new GregorianCalendar();

            int distancia = recorrido.obtenerOrden(siguiente)-recorrido.obtenerOrden(dato.getParada());
            int ajusteError = calcularCorreccion(distancia);

            horaArribo.setTimeInMillis(prediccion.getHoraPrediccion().getTimeInMillis()+
                    obtenerPrediccion(dato.getParada(), siguiente)*1000-ajusteError*1000);
            prediccion.setHoraArriboEstimada(horaArribo);

            p.add(prediccion);
            siguiente = recorrido.obtenerParadaSiguiente(siguiente.getId());
        }
    }

    private void calcularErroresPrediccion(ArrayList<DatoProcesado> datos) throws SQLException {
        //funcion que corrige los errores, los va calculando y los almacena
        ArrayList<Prediccion> p = (ArrayList<Prediccion>)predicciones.get(datos.get(0).getIdcolectivo());

        if(p != null) {
            for(int i = 0; i < datos.size(); i++) {
                ArrayList<Prediccion> prediccionesyErrores = new ArrayList<Prediccion>(0);

                int j = p.size()-1;
                long toleranciaMilis = 50 * 60 * 1000; // 50 minutos
                long diferenciaTiempo = (datos.get(i).getHoraArribo().getTimeInMillis() -
                        p.get(j).getHoraArriboEstimada().getTimeInMillis());

                while(j > 0) {
                    diferenciaTiempo = (datos.get(i).getHoraArribo().getTimeInMillis() -
                        p.get(j).getHoraArriboEstimada().getTimeInMillis());
                    if(datos.get(i).getParada().getId() == p.get(j).getDestino().getId()) {
                        if(diferenciaTiempo < toleranciaMilis) {
                            p.get(j).setHoraArriboReal(datos.get(i).getHoraArribo());
                            extraerDatosParaRegresionLineal(p.get(j));
                            prediccionesyErrores.add(p.remove(j));
                        }
                    }
                    j--;
                }
                this.guardarPrediccion(prediccionesyErrores);
            }
            calcularRectaAjuste();
        }
    }

    public void guardarPrediccion(ArrayList<Prediccion> p) throws SQLException {
        //Esta funcion guarda las nuevas estimaciones en la Base de Datos
        Connection conexionBD = null;

          try {
            conexionBD = DriverManager.getConnection("jdbc:postgresql://localhost/sistemaprediccion",
                                            "postgres", "postgres");
          }
          catch (SQLException se) {
            System.out.println("Couldn't connect: print out a stack trace and exit.");
            se.printStackTrace();
            System.exit(1);
          }

        Statement s = null;
        try {
          s = conexionBD.createStatement();
        }
        catch (SQLException se) {
          System.out.println("Error en la BD");
          se.printStackTrace();
          System.exit(1);
        }

        String consulta="";

        for(int i=0; i<p.size(); i++) {
            int origen = this.recorrido.obtenerOrden(p.get(i).getOrigen());
            int destino = this.recorrido.obtenerOrden(p.get(i).getDestino());

            consulta += "INSERT INTO motorprediccion.prediccionesyerrores("+
            "idcolectivo, paradaorigen, paradadestino, paradasdistancia, "+
            " horaestimacion, arriboestimado, arriboreal) "+
            "VALUES ("+
                p.get(i).getIdColectivo() + ", "+
                origen + ", "+
                destino + ", "+
                (destino-origen)+", "+
                "to_timestamp("+p.get(i).getHoraPrediccion().getTimeInMillis()/1000+"),"+
                "to_timestamp("+p.get(i).getHoraArriboEstimada().getTimeInMillis()/1000+"),"+
                "to_timestamp("+p.get(i).getHoraArriboReal().getTimeInMillis()/1000+"));\n";
        }

            if(consulta.length()>0) {
                try {
                  s.executeUpdate(consulta);
                }
                catch (SQLException se) {
                  System.out.println("SQL Error");
                  se.printStackTrace();
                  System.exit(1);
                }
            }
        conexionBD.close();
        }

    private void extraerDatosParaRegresionLineal(Prediccion prediccion) {
        //funcion que mantiene el limite de los datos para la regresion lineal
        x.add(recorrido.obtenerOrden(prediccion.getDestino()) - recorrido.obtenerOrden(prediccion.getOrigen()));
        y.add((float)(-prediccion.getHoraArriboReal().getTimeInMillis()+
                prediccion.getHoraArriboEstimada().getTimeInMillis())/1000);

        while(x.size()>300) {
            x.remove(0);
            y.remove(0);
        }
    }

    private void calcularRectaAjuste() {
        //Funcion que va calculando el corrector
        if(x.size()>100) {
            float sumX = 0, sumXY = 0, sumXX = 0, sumY = 0;
            int n = x.size();

            for(int i = 0; i < n; i++) {
                sumX += x.get(i);
                sumXY += x.get(i)*y.get(i);
                sumXX += x.get(i)*x.get(i);
                sumY += y.get(i);
            }

            m = (n*sumXY-sumX*sumY)/(n*sumXX-sumX*sumX);
            b = (sumY-m*sumX)/n;
        }
    }

    private int calcularCorreccion(int distancia) {
        //Dejar separado porque se puede dar un mejor modelo de correccion
        return (int)(m*distancia+b);
    }

    public int estimarTiempos(ArrayList<DatoPosicional> Posiciones){
        //Funcion heredada, falta implementar y rediccionar a otras funciones
        return 0;
    }
}
